/*
 * Copyright 2011 Angel Sanadinov
 *
 * This file is part of VBox WMI.
 *
 * VBox WMI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * VBox WMI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with VBox WMI.  If not, see <http://www.gnu.org/licenses/>.
 */

package Services;

import Beans.Requests.AuthorizationRequest;
import Beans.Requests.SimpleAuthorizationRequest;
import Beans.Requests.MediumManagement.OpenDVDRequest;
import Beans.Requests.MediumManagement.CloneDiskRequest;
import Beans.Requests.MediumManagement.ResetDiskRequest;
import Beans.Requests.MediumManagement.ResizeDiskRequest;
import Beans.Requests.MediumManagement.MergeDisksRequest;
import Beans.Requests.MediumManagement.CompactDiskRequest;
import Beans.Requests.MediumManagement.OpenHardDiskRequest;
import Beans.Requests.MediumManagement.RemoveMediumRequest;
import Beans.Requests.MediumManagement.ModifyMediumRequest;
import Beans.Requests.MediumManagement.OpenFloppyDiskRequest;
import Beans.Requests.MediumManagement.CreateHardDiskRequest;
import Beans.Requests.MediumManagement.MediumManagementRequest;
import Beans.Requests.RequestBean;
import Beans.Responses.MediumManagementResponse;
import Beans.VirtualResourceBean;
import Managers.LogsManager;
import Managers.DatabaseManager;
import Managers.PermissionsManager;
import Managers.VBoxConnectionsManager;
import Managers.Utilities.DatabaseManipulationResult;
import Managers.Utilities.RequestResponseWrapper;
import Managers.Utilities.ResponseMessagesContainer;
import Utilities.Constants;
import Utilities.Constants.VirtualResourceType;
import Utilities.ApplicationException;
import Utilities.ExceptionMessages;
import Utilities.Functions;
import Utilities.ParameterNames;
import Utilities.PermissionsConstants;
import com.sun.xml.ws.commons.virtualbox_3_2.IMedium;
import com.sun.xml.ws.commons.virtualbox_3_2.IProgress;
import com.sun.xml.ws.commons.virtualbox_3_2.IVirtualBox;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.WebServiceException;

/**
 * This servlet represents the application's Media Management Service. <br><br>
 *
 * This service takes care of the media creation, removal and modification (cloning,
 * compacting, merging, reseting, resizing, etc).
 *
 * @author Angel Sanadinov
 */
public class MediaManagementService extends HttpServlet
{
    private DatabaseManager databaseManager;             //database manager
    private LogsManager logsManager;                     //logs manager
    private PermissionsManager permissionsManager;       //permissions manager
    private VBoxConnectionsManager connectionsManager;   //virtualbox connections manager
    private int maxOperationWaitTime; //max time in ms to wait for VirtualBox operation completion

    /**
     * Initializes the service and retrieves all necessary managers.
     *
     * @throws ServletException if an exception occurs that interrupts the servlet's
     *                          normal operation
     *
     * @see HttpServlet#init()
     */
    @Override
    public void init() throws ServletException
    {
        ServletContext context = getServletContext();
        databaseManager = (DatabaseManager)context.getAttribute(Constants.CONTEXT_DATABASE_MANAGER);
        logsManager = (LogsManager)context.getAttribute(Constants.CONTEXT_LOGS_MANAGER);
        permissionsManager = (PermissionsManager)context.getAttribute(Constants.CONTEXT_PERMISSIONS_MANAGER);
        connectionsManager = (VBoxConnectionsManager)context.getAttribute(Constants.CONTEXT_VBOX_CONNECTIONS_MANAGER);

        maxOperationWaitTime = 0;
        try
        {
            maxOperationWaitTime =
                    Integer.parseInt(context.getInitParameter(ParameterNames.MAX_OPERATION_WAIT_TIME));
        }
        catch(NumberFormatException e)
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_WARNING,
                                       "Media Management Service: Failed to parse initialization parameter"
                                       + " Default value (" + maxOperationWaitTime
                                       + ") for maxOperationWaitTime will be used.");
        }
    }

    /**
     * Shuts down the service and cleans up any object references.
     *
     * @see HttpServlet#destroy()
     */
    @Override
    public void destroy()
    {
        databaseManager = null;
        logsManager = null;
        permissionsManager = null;
        connectionsManager = null;
    }

    /**
     * Processes a <code>GET</code> request from a client.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @throws ServletException if the request for the GET could not be handled
     * @throws IOException if an input or output error is detected when the servlet
     *         handles the GET request
     *
     * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        processRequest(request, response);
    }

    /**
     * Processes a <code>POST</code> request from a client.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @throws ServletException if the request for the POST could not be handled
     * @throws IOException if an input or output error is detected when the servlet
     *         handles the POST request
     *
     * @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        processRequest(request, response);
    }

    /**
     * Processes all <code>GET</code> and <code>POST</code> requests and carries out
     * operations based on requested actions.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @throws ServletException if the request could not be handled
     * @throws IOException if an input or output error is detected when the service
     *         handles the request
     */
    private void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        //retrieves the medium management request
        MediumManagementRequest mediumRequest =
                (MediumManagementRequest)request.getAttribute(ParameterNames.REQUEST_MANAGE_MEDIUM);

        //checks that the request is available and is valid
        if(mediumRequest != null && mediumRequest.isValid())
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                           "Request: " + mediumRequest.toString());
            //attempts to authorize the request
            boolean isActionAuthorized = false;
            switch(mediumRequest.getRequestedAction()) //builds the authorization request object...
            {
                //no medium UUIDs are available, therefore a simple authorization request is made
                case PermissionsConstants.ACTION_MEDIUM_CREATE_HDD:
                case PermissionsConstants.ACTION_MEDIUM_CREATE_HDD_DIFF:
                case PermissionsConstants.ACTION_MEDIUM_OPEN_HDD:
                case PermissionsConstants.ACTION_MEDIUM_OPEN_DVD:
                case PermissionsConstants.ACTION_MEDIUM_OPEN_FDD:
                    isActionAuthorized = permissionsManager.isActionAuthorized(
                                            new SimpleAuthorizationRequest(mediumRequest.getRequestorId(),
                                                                           mediumRequest.getServerId(),
                                                                           mediumRequest.getRequestedAction(),
                                                                           VirtualResourceType.MEDIUM));
                    break;

                //medium UUIDs should be available...
                default:
                {
                    isActionAuthorized = permissionsManager.isActionAuthorized(
                                                new AuthorizationRequest(mediumRequest.getRequestorId(),
                                                                         mediumRequest.getServerId(),
                                                                         mediumRequest.getRequestedAction(),
                                                                         VirtualResourceType.MEDIUM,
                                                                         mediumRequest.getMediumId()));
                } break;
            }

            //<editor-fold defaultstate="collapsed" desc="Action Selection">
            //checks if the request is authorized...
            if(isActionAuthorized)
            {
                //carries out the appropriate action based on the request data
                switch(mediumRequest.getRequestedAction())
                {
                    //a new hard disk is to be created (normal or differencing)
                    case PermissionsConstants.ACTION_MEDIUM_CREATE_HDD:
                    case PermissionsConstants.ACTION_MEDIUM_CREATE_HDD_DIFF:
                    {
                        processResponse(request, createHardDisk((CreateHardDiskRequest)mediumRequest));
                    } break;

                    //a hard disk is to be opened and registered on the system
                    case PermissionsConstants.ACTION_MEDIUM_OPEN_HDD:
                    {
                        processResponse(request, openHardDisk((OpenHardDiskRequest)mediumRequest));
                    } break;

                    //a dvd is to be opened and registered on the system
                    case PermissionsConstants.ACTION_MEDIUM_OPEN_DVD:
                    {
                        processResponse(request, openDVD((OpenDVDRequest)mediumRequest));
                    } break;

                    //a floppy disk is to be opened and registered on the system
                    case PermissionsConstants.ACTION_MEDIUM_OPEN_FDD:
                    {
                        processResponse(request, openFloppyDisk((OpenFloppyDiskRequest)mediumRequest));
                    } break;

                    //hard disks are to be merged
                    case PermissionsConstants.ACTION_MEDIUM_MERGE:
                    {
                        processResponse(request, mergeDisks((MergeDisksRequest)mediumRequest));
                    } break;

                    //a hard disk is to be cloned
                    case PermissionsConstants.ACTION_MEDIUM_CLONE:
                    {
                        processResponse(request, cloneDisk((CloneDiskRequest)mediumRequest));
                    } break;

                    //a hard disk is to be compacted
                    case PermissionsConstants.ACTION_MEDIUM_COMPACT:
                    {
                        processResponse(request, compactDisk((CompactDiskRequest)mediumRequest));
                    } break;

                    //a hard disk is to be resized
                    case PermissionsConstants.ACTION_MEDIUM_RESIZE:
                    {
                        processResponse(request, resizeDisk((ResizeDiskRequest)mediumRequest));
                    } break;

                    //a hard disk is to be reset
                    case PermissionsConstants.ACTION_MEDIUM_RESET:
                    {
                        processResponse(request, resetDisk((ResetDiskRequest)mediumRequest));
                    } break;

                    //an existing medium is to be modified
                    case PermissionsConstants.ACTION_MEDIUM_MODIFY:
                    {
                        processResponse(request, modifyMedium((ModifyMediumRequest)mediumRequest));
                    } break;

                    //an existing medium is to be removed
                    case PermissionsConstants.ACTION_MEDIUM_REMOVE:
                    {
                        processResponse(request, removeMedium((RemoveMediumRequest)mediumRequest));
                    } break;

                    default:
                    {
                        processResponse(request, new MediumManagementResponse(false,
                                new ApplicationException(ExceptionMessages.APP_E_INVALID_REQUESTED_ACTION)));
                    } break;
                }

            }
            else
            {
                processResponse(request, new MediumManagementResponse(false, 
                        new ApplicationException(ExceptionMessages.APP_E_OPERATION_NOT_ALLOWED)));
            }
            //</editor-fold>
        }
        else
        {
            processResponse(request, new MediumManagementResponse(false, 
                    new ApplicationException(ExceptionMessages.APP_E_INVALID_REQUEST)));
        }
    }

    /**
     * Processes all responses going out of the service.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @param mediumResponse medium management response object
     * @param forwardRequest set to <code>true</code> if the service is to forward
     *                       the request to another page (default action)
     * @throws ServletException if the processing of the response could not be handled
     * @throws IOException if an input or output error is detected when the response
     *                     is handled
     */
    private void processResponse(HttpServletRequest request , MediumManagementResponse mediumResponse)
            throws ServletException, IOException
    {
        //retrieves the request object from the session
        RequestBean requestData = (RequestBean)request.getAttribute(ParameterNames.REQUEST_MANAGE_MEDIUM);
        request.removeAttribute(ParameterNames.REQUEST_MANAGE_MEDIUM);
        request.setAttribute(ParameterNames.RESPONSE_MANAGE_MEDIUM, mediumResponse);

        //checks if the requested action failed
        if(!mediumResponse.getRequestResult()
                || (mediumResponse.getExceptions() != null && !mediumResponse.getExceptions().isEmpty()))
        {
            //the request failed; retrieves the message container and stores the request/response pair
            ResponseMessagesContainer messagesContainer = (ResponseMessagesContainer)
                    request.getSession(false).getAttribute(ParameterNames.SESSION_REQUEST_MESSAGES_OBJECT);

            messagesContainer.put(new RequestResponseWrapper(requestData, mediumResponse));

            if(mediumResponse.getExceptions() == null)
            {
                logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                           "Request execution failed: "
                                           + mediumResponse.getException().toString());
            }
            else
            {
                for(ApplicationException exception : mediumResponse.getExceptions())
                {
                    logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                       "Request execution failed: " + exception.toString());
                }
            }
        }
        else
            ;
    }

    /**
     * Creates a new hard disk.
     *
     * @param request hard disk creating request
     * @return a response to the medium management request
     */
    private MediumManagementResponse createHardDisk(CreateHardDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium disk = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //attempts to create the hard disk
                disk = vbox.createHardDisk(request.getFormat(), request.getLocation());
                passedPreOp = true;
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress storageCreationProgress = null;

        try
        {
            //checks which storage creation operation to perform
            switch(request.getRequestedAction())
            {
                //a normal hard disk storage it so be created
                case PermissionsConstants.ACTION_MEDIUM_CREATE_HDD:
                {
                    storageCreationProgress =
                            disk.createBaseStorage(BigInteger.valueOf(request.getLogicalSize()),
                                                   request.getVarinat());
                } break;

                //a differencing disk storage is to be created
                case PermissionsConstants.ACTION_MEDIUM_CREATE_HDD_DIFF:
                {
                    IMedium parent = vbox.getHardDisk(request.getParentId());

                    storageCreationProgress =
                            parent.createDiffStorage(disk, request.getVarinat());
                } break;

                default:
                    opException = new ApplicationException(ExceptionMessages.APP_E_MEDIUM_INVALID_REQUESTED_ACTION); break;
            }

            if(storageCreationProgress != null)
                passedOp = true;
            else if(opException == null)
                opException = new ApplicationException(ExceptionMessages.APP_E_PROGRESS_OBJECT_RETRIEVAL_FAILED);
            else
                ;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, storageCreationProgress);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="PostOp">
        //PostOp ##########################################################################
        boolean passedPostOp = false;
        ApplicationException postOpException = null;

        //attempts to add the new hard disk to the database
        DatabaseManipulationResult result =
                            databaseManager.insertVirtualResource(new VirtualResourceBean(disk.getId(),
                                                                                          request.getServerId(),
                                                                                          request.getRequestorId(),
                                                                                          request.getPermissions(),
                                                                                          VirtualResourceType.MEDIUM));

        if(result.wasOperationSuccessful()) //was the operation successful?
            passedPostOp = true;
        else if(result.getErrorCode() == Constants.SQL_ERROR_ENTRY_EXISTS)
        {
            postOpException = new ApplicationException(ExceptionMessages.APP_E_HARD_DISK_EXISTS_IN_DB);
            disk.close();
        }
        else
        {
            postOpException = new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                       "(" + result.getErrorCode() + ")");
        }
       
        if(!passedPostOp)
            return new MediumManagementResponse(false, postOpException, storageCreationProgress);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, null, storageCreationProgress, disk.getId());
    }

    /**
     * Opens an existing hard disk on the host.
     *
     * @param request hard disk opening request
     * @return a response to the medium management request
     */
    private MediumManagementResponse openHardDisk(OpenHardDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="Op & PostOp">
        //Op And PostOp ##########################################################################
        boolean opAndPostOp = false;
        ApplicationException opAndPostOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium disk = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //attempts to open an existing hard disk using the specified data
                disk = vbox.openHardDisk(request.getLocation(),
                                         request.getAccessMode(),
                                         request.isImageIdSet(),
                                         request.getImageId(),
                                         request.isParentIdSet(),
                                         request.getParentId());

                //attempts to add the hard disk to the database
                DatabaseManipulationResult result =
                            databaseManager.insertVirtualResource(new VirtualResourceBean(disk.getId(),
                                                                                          request.getServerId(),
                                                                                          request.getRequestorId(),
                                                                                          request.getPermissions(),
                                                                                          VirtualResourceType.MEDIUM));

                if(result.wasOperationSuccessful()) //was the operation successful?
                    opAndPostOp = true;
                else if(result.getErrorCode() == Constants.SQL_ERROR_ENTRY_EXISTS)
                {
                    opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_HARD_DISK_EXISTS_IN_DB);
                    disk.close();
                }
                else
                {
                    opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                                    "(" + result.getErrorCode() + ")");
                    disk.close();
                }
            }
            catch(WebServiceException e)
            {
                opAndPostOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!opAndPostOp)
            return new MediumManagementResponse(false, opAndPostOpException);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, (ApplicationException)null);
    }

    /**
     * Creates a new DVD.
     *
     * @param request DVD creation request
     * @return a response to the medium management request
     */
    private MediumManagementResponse openDVD(OpenDVDRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="Op & PostOp">
        //Op And PostOp ##########################################################################
        boolean opAndPostOp = false;
        ApplicationException opAndPostOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium dvd = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //attempts to open the DVD image
                dvd = vbox.openDVDImage(request.getLocation(), request.getImageId());

                //attempts to add the medium data to the database
                DatabaseManipulationResult result =
                            databaseManager.insertVirtualResource(new VirtualResourceBean(dvd.getId(),
                                                                                          request.getServerId(),
                                                                                          request.getRequestorId(),
                                                                                          request.getPermissions(),
                                                                                          VirtualResourceType.MEDIUM));

                if(result.wasOperationSuccessful()) //was the operation successful?
                    opAndPostOp = true;
                else if(result.getErrorCode() == Constants.SQL_ERROR_ENTRY_EXISTS)
                {
                    opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_MEDIUM_EXISTS_IN_DB);
                    dvd.close();
                }
                else
                {
                    opAndPostOpException= new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                                   "(" + result.getErrorCode() + ")");
                    dvd.close();
                }
            }
            catch(WebServiceException e)
            {
                opAndPostOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!opAndPostOp)
            return new MediumManagementResponse(false, opAndPostOpException);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, (ApplicationException)null);
    }

    /**
     * Creates a new floppy disk.
     *
     * @param request floppy disk creation request
     * @return a response to the medium management request
     */
    private MediumManagementResponse openFloppyDisk(OpenFloppyDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="Op & PostOp">
        //Op And PostOp ##########################################################################
        boolean opAndPostOp = false;
        ApplicationException opAndPostOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium floppy = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //attempts to open the floppy disk image
                floppy = vbox.openFloppyImage(request.getLocation(), request.getImageId());

                //attempts to add the medium data to the database
                DatabaseManipulationResult result =
                            databaseManager.insertVirtualResource(new VirtualResourceBean(floppy.getId(),
                                                                                          request.getServerId(),
                                                                                          request.getRequestorId(),
                                                                                          request.getPermissions(),
                                                                                          VirtualResourceType.MEDIUM));

                if(result.wasOperationSuccessful()) //was the operation successful?
                    opAndPostOp = true;
                else if(result.getErrorCode() == Constants.SQL_ERROR_ENTRY_EXISTS)
                {
                    opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_MEDIUM_EXISTS_IN_DB);
                    floppy.close();
                }
                else
                {
                    opAndPostOpException= new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                                   "(" + result.getErrorCode() + ")");
                    floppy.close();
                }
            }
            catch(WebServiceException e)
            {
                opAndPostOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            opAndPostOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!opAndPostOp)
            return new MediumManagementResponse(false, opAndPostOpException);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, (ApplicationException)null);
    }

    /**
     * Modifies an existing medium
     *
     * @param request medium modification request
     * @return a response to the medium management request
     */
    private MediumManagementResponse modifyMedium(ModifyMediumRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium medium = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the medium based on the device type
                switch(request.getDeviceType())
                {
                    //a hard disk is to modified
                    case HARD_DISK:
                    {
                        medium = vbox.getHardDisk(request.getMediumId());
                        passedPreOp = true;
                    } break;

                    //a dvd is to be modified
                    case DVD:
                    {
                        medium = vbox.getDVDImage(request.getMediumId());
                        passedPreOp = true;
                    } break;

                    //a floppy disk is to be modified
                    case FLOPPY:
                    {
                        medium = vbox.getFloppyImage(request.getMediumId());
                        passedPreOp = true;
                    } break;

                    default:
                        preOpException = new ApplicationException(ExceptionMessages.APP_E_DEVICE_IS_NOT_MEDIUM); break;
                }
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ArrayList<ApplicationException> opExceptions = new ArrayList<ApplicationException>();

        try
        {
            //checks if a new description is to be set
            if(request.isDescriptionSet())
            {
                medium.setDescription(request.getDescription());
                passedOp = true;
            }
            else
                ;

            //checks if a new location is to be set
            if(request.isLocationSet())
            {
                medium.setLocation(request.getLocation());
                passedOp = true;
            }
            else
                ;

            //checks if a new medium type is to be set
            if(request.isTypeSet())
            {
                medium.setType(request.getType());
                passedOp = true;
            }
            else
                ;

            //checks if the auto-reset status is to be set
            if(request.isAutoResetSet())
            {
                medium.setAutoReset(request.getAutoReset());
                passedOp = true;
            }
            else
                ;

            //checks if new permissions are to be set
            if(request.arePermissionsSet())
            {
                //checks if this medium is the base/parent
                if(medium.getParent() == null)
                {
                    //attempts to modify the permissions
                    DatabaseManipulationResult result =
                            databaseManager.updateVirtualResourcePermissions(medium.getId(),
                                                                             request.getServerId(),
                                                                             request.getPermissions());

                    if(result.wasOperationSuccessful()) //was the operation successful?
                        passedOp = true;
                    else if(result.getErrorCode() == Constants.SQL_ERROR_NOT_UPDATED)
                    {
                        passedOp = false;
                        opExceptions.add(new ApplicationException(ExceptionMessages.APP_E_MEDIUM_PERMISSIONS_UPDATE_FAILED));
                    }
                    else
                    {
                        passedOp = false;
                        opExceptions.add(new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                                  "(" + result.getErrorCode() + ")"));
                    }
                }
                else
                {
                    passedOp = false;
                    opExceptions.add(new ApplicationException(ExceptionMessages.APP_E_MEDIUM_NOT_BASE));
                }
            }
            else
                ;

            //checks if a new owner is to be set
            if(request.isNewOwnerIdSet())
            {
                //checks if this medium is the base/parent
                if(medium.getParent() == null)
                {
                    //attempts to set the new owner
                    DatabaseManipulationResult result =
                            databaseManager.changeVirtualResourceOwner(medium.getId(),
                                                                       request.getServerId(),
                                                                       request.getNewOwnerId());

                    if(result.wasOperationSuccessful()) //was the operation successful?
                        passedOp = true;
                    else if(result.getErrorCode() == Constants.SQL_ERROR_NOT_UPDATED)
                    {
                        passedOp = false;
                        opExceptions.add(new ApplicationException(ExceptionMessages.APP_E_MEDIUM_OWNER_UPDATE_FAILED));
                    }
                    else
                    {
                        passedOp = false;
                        opExceptions.add(new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                                  "(" + result.getErrorCode() + ")"));
                    }
                }
                else
                {
                    passedOp = false;
                    opExceptions.add(new ApplicationException(ExceptionMessages.APP_E_MEDIUM_NOT_BASE));
                }
            }
            else
                ;
        }
        catch(WebServiceException e)
        {
            passedOp = false;
            opExceptions.add(Functions.parseVirtualBoxException(e));
        }

        if(!passedOp)
            return new MediumManagementResponse(opExceptions);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="PostOp">
        //PostOp ##########################################################################

        //notifies the permissions manager that changes might have occured
        permissionsManager.updateResourceCache(medium.getId(), request.getServerId());
        //</editor-fold>
        
        //Result ##########################################################################
        return new MediumManagementResponse(true, (ApplicationException)null);
    }

    /**
     * Merges two existing disks.
     *
     * @param request disks merge request
     * @return a response to the medium management request
     */
    private MediumManagementResponse mergeDisks(MergeDisksRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium sourceDisk = null;
        IMedium targetDisk = null;
        boolean replaceSourceWithTarget = false; //determines whether the source's database
                                                 //data should be replaced by the target's data

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the source and target disks
                sourceDisk = vbox.getHardDisk(request.getMediumId());
                targetDisk = vbox.getHardDisk(request.getTargetId());

                //checks if the source disk has a parent
                if(sourceDisk.getParent() == null)
                    replaceSourceWithTarget = true;
                else
                    ;

                //checks if the operation is allowed for the target medium
                boolean isActionAuthorizedOnTarget =
                        permissionsManager.isActionAuthorized(
                            new AuthorizationRequest(request.getRequestorId(),
                                                     request.getServerId(),
                                                     PermissionsConstants.ACTION_MEDIUM_MERGE,
                                                     VirtualResourceType.MEDIUM,
                                                     request.getTargetId()));

                if(isActionAuthorizedOnTarget)
                    passedPreOp = true;
                else
                    preOpException = new ApplicationException(ExceptionMessages.APP_E_OPERATION_NOT_ALLOWED);
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress mediaMergeProgress = null;

        try
        {
            //starts the merging process
            mediaMergeProgress = sourceDisk.mergeTo(targetDisk);

            if(mediaMergeProgress != null)
                passedOp = true;
            else if(opException == null)
                opException = new ApplicationException(ExceptionMessages.APP_E_PROGRESS_OBJECT_RETRIEVAL_FAILED);
            else
                ;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, mediaMergeProgress);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="PostOp">
        //PostOp ##########################################################################
        boolean passedPostOp = false;
        ApplicationException postOpException = null;

        if(replaceSourceWithTarget)
        {
            //retrieves the source's data
            VirtualResourceBean sourceData = databaseManager.getVirtualResource(request.getMediumId(),
                                                                                request.getServerId(),
                                                                                VirtualResourceType.MEDIUM);

            //builds the target's data based on the source
            VirtualResourceBean targetData = new VirtualResourceBean(request.getTargetId(),
                                                                     request.getServerId(),
                                                                     sourceData.getOwnerId(),
                                                                     sourceData.getPermissions(),
                                                                     VirtualResourceType.MEDIUM);

            //removes the source's data
            DatabaseManipulationResult removeSource = databaseManager.removeVirtualResource(sourceData.getResourceId(),
                                                                                            sourceData.getServerId());

            //adds the target's data
            DatabaseManipulationResult addTarget = databaseManager.insertVirtualResource(targetData);

            if(addTarget.wasOperationSuccessful()) //was the operation successful?
                passedPostOp = true;
            else if(addTarget.getErrorCode() == Constants.SQL_ERROR_ENTRY_EXISTS)
                postOpException = new ApplicationException(ExceptionMessages.APP_E_MEDIUM_EXISTS_IN_DB);
            else if(!removeSource.wasOperationSuccessful()
                    && removeSource.getErrorCode() == Constants.SQL_ERROR_NOT_REMOVED)
            {
                postOpException = new ApplicationException(ExceptionMessages.APP_E_MEDIUM_SOURCE_REMOVAL_FAILED);
            }
            else
            {
                postOpException = new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                        "Target (" + addTarget.getErrorCode() + ") / Source (" + removeSource.getErrorCode() + ")");
            }
        }
        else
            passedOp = true;

        if(!passedPostOp)
            return new MediumManagementResponse(false, postOpException, mediaMergeProgress);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, null, mediaMergeProgress);
    }

    /**
     * Clones one disk to another.
     *
     * @param request disk cloning request
     * @return a response to the medium management request
     */
    private MediumManagementResponse cloneDisk(CloneDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium sourceDisk = null;
        IMedium targetDisk = null;
        IMedium parentDisk = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the source and target disks
                sourceDisk = vbox.getHardDisk(request.getMediumId());
                targetDisk = vbox.getHardDisk(request.getTargetId());

                //retrieves the parent's disk, if required
                if(request.getParentId() != null)
                    parentDisk = vbox.getHardDisk(request.getParentId());
                else
                    ;

                //checks if the operation is allowed for the target medium
                boolean isActionAuthorizedOnTarget =
                        permissionsManager.isActionAuthorized(
                            new AuthorizationRequest(request.getRequestorId(),
                                                     request.getServerId(),
                                                     PermissionsConstants.ACTION_MEDIUM_CLONE,
                                                     VirtualResourceType.MEDIUM,
                                                     request.getTargetId()));

                if(isActionAuthorizedOnTarget)
                    passedPreOp = true;
                else
                    preOpException = new ApplicationException(ExceptionMessages.APP_E_OPERATION_NOT_ALLOWED);
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress mediumCloneProgress = null;

        try
        {
            //starts the cloning process
            mediumCloneProgress = sourceDisk.cloneTo(targetDisk, request.getVariant(), parentDisk);

            if(mediumCloneProgress != null)
                passedOp = true;
            else if(opException == null)
                opException = new ApplicationException(ExceptionMessages.APP_E_PROGRESS_OBJECT_RETRIEVAL_FAILED);
            else
                ;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, mediumCloneProgress);
        else
            ;
        //</editor-fold>
        
        //Result ##########################################################################
        return new MediumManagementResponse(true, null, mediumCloneProgress);
    }

    /**
     * Compacts a disk.
     *
     * @param request disk compacting request
     * @return a response to the medium management request
     */
    private MediumManagementResponse compactDisk(CompactDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium disk = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the disk for compacting
                disk = vbox.getHardDisk(request.getMediumId());
                passedPreOp = true;
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress mediumCompactProgress = null;

        try
        {
            //starts the compacting process
            mediumCompactProgress = disk.compact();

            if(mediumCompactProgress != null)
                passedOp = true;
            else if(opException == null)
                opException = new ApplicationException(ExceptionMessages.APP_E_PROGRESS_OBJECT_RETRIEVAL_FAILED);
            else
                ;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, mediumCompactProgress);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, null, mediumCompactProgress);
    }

    /**
     * Resizes a disk.
     *
     * @param request disk resizing request
     * @return a response to the medium management request
     */
    private MediumManagementResponse resizeDisk(ResizeDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium disk = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the disk for resizing
                disk = vbox.getHardDisk(request.getMediumId());
                passedPreOp = true;
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress mediumResizeProgress = null;

        try
        {
            //starts the resizing operation
            mediumResizeProgress = disk.resize(BigInteger.valueOf(request.getNewSize()));

            if(mediumResizeProgress != null)
                passedOp = true;
            else if(opException == null)
                opException = new ApplicationException(ExceptionMessages.APP_E_PROGRESS_OBJECT_RETRIEVAL_FAILED);
            else
                ;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, mediumResizeProgress);
        else
            ;
        //</editor-fold>
        
        //Result ##########################################################################
        return new MediumManagementResponse(true, null, mediumResizeProgress);
    }

    /**
     * Resets a disk. <br><br>
     *
     * <b>Note:</b> <i>For differencing disks only.</i>
     *
     * @param request disk resetting request
     * @return a response to the medium management request
     */
    private MediumManagementResponse resetDisk(ResetDiskRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium disk = null;

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the disk for resetting
                disk = vbox.getHardDisk(request.getMediumId());
                passedPreOp = true;
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress mediumResetProgress = null;

        try
        {
            //starts the resetting operation
            mediumResetProgress = disk.reset();

            if(mediumResetProgress != null)
                passedOp = true;
            else if(opException == null)
                opException = new ApplicationException(ExceptionMessages.APP_E_PROGRESS_OBJECT_RETRIEVAL_FAILED);
            else
                ;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, mediumResetProgress);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, null, mediumResetProgress);
    }

    /**
     * Removes a medium.
     *
     * @param request medium removal request
     * @return a response to the medium management request
     */
    private MediumManagementResponse removeMedium(RemoveMediumRequest request)
    {
        //<editor-fold defaultstate="collapsed" desc="PreOp">
        //PreOp ##########################################################################
        boolean passedPreOp = false;
        ApplicationException preOpException = null;

        //attemts to retrieve the virtualbox object for the required server
        IVirtualBox vbox = connectionsManager.getVirtualBoxReference(request.getServerId(), request.getRequestorId());
        IMedium medium = null;
        boolean removeFromDatabase = false; //should the medium data be removed from the database?
        boolean deleteStorage = false;      //should the base storage be removed? (for hard disks only)

        //checks if the virtualbox object was retrieved
        if(vbox != null)
        {
            try
            {
                //retrieves the medium for removal, based on the device type
                switch(request.getDeviceType())
                {
                    case HARD_DISK:
                    {
                        medium = vbox.getHardDisk(request.getMediumId());

                        //sets the storage removal flag
                        deleteStorage = request.getDeleteStorage();

                        //checks if this disk is the parent
                        if(medium.getParent() == null)
                            removeFromDatabase = true;
                        else
                            ;

                        passedPreOp = true;
                    } break;

                    case DVD:
                    {
                        medium = vbox.getDVDImage(request.getMediumId());
                        removeFromDatabase = true;
                        passedPreOp = true;
                    } break;

                    case FLOPPY:
                    {
                        medium = vbox.getFloppyImage(request.getMediumId());
                        removeFromDatabase = true;
                        passedPreOp = true;
                    } break;

                    default:
                        preOpException = new ApplicationException(ExceptionMessages.APP_E_DEVICE_IS_NOT_MEDIUM); break;
                }
            }
            catch(WebServiceException e)
            {
                preOpException = Functions.parseVirtualBoxException(e);
            }
        }
        else
            preOpException = new ApplicationException(ExceptionMessages.APP_E_VBOX_REF_NOT_AVAILABLE);

        if(!passedPreOp)
            return new MediumManagementResponse(false, preOpException);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="Op">
        //Op ##########################################################################
        boolean passedOp = false;
        ApplicationException opException = null;

        IProgress mediumRemovalProgress = null;

        try
        {
            //TODO: this whole block is very likely to fail if a large disk is to
            //      be removed; ie: the operation running longer that the max time
            //starts removing the medium storage, if required
            if(deleteStorage)
            {
                mediumRemovalProgress = medium.deleteStorage();
                
                //waits for the storage removal operation to complete or for the maximum wait time
                mediumRemovalProgress.waitForCompletion(maxOperationWaitTime);
            }
            else
                ;

            //closes the medium
            medium.close();

            passedOp = true;
        }
        catch(WebServiceException e)
        {
            opException = Functions.parseVirtualBoxException(e);
        }

        if(!passedOp)
            return new MediumManagementResponse(false, opException, mediumRemovalProgress);
        else
            ;
        //</editor-fold>

        //<editor-fold defaultstate="collapsed" desc="PostOp">
        //PostOp ##########################################################################
        boolean passedPostOp = false;
        ApplicationException postOpException = null;

        //checks if the medium data is to be removed from the database
        if(removeFromDatabase)
        {
            //attempts to remove the medium data
            DatabaseManipulationResult result = databaseManager.removeVirtualResource(request.getMediumId(),
                                                                                      request.getServerId());

            if(result.wasOperationSuccessful()) //was the operation successful?
                passedPostOp = true;
            else if(result.getErrorCode() == Constants.SQL_ERROR_NOT_REMOVED)
                postOpException = new ApplicationException(ExceptionMessages.APP_E_MEDIUM_REMOVAL_FAILED);
            else
                postOpException = new ApplicationException(ExceptionMessages.APP_E_DATABASE_EXCEPTION,
                                                           "(" + result.getErrorCode() + ")");
        }
        else
            passedPostOp = true;

        if(!passedPostOp)
            return new MediumManagementResponse(false, postOpException, mediumRemovalProgress);
        else
            ;
        //</editor-fold>

        //Result ##########################################################################
        return new MediumManagementResponse(true, null, mediumRemovalProgress);

    }
}
